Session 2 Part 2

Now we need to change the simulation tasks to use the partitions we've created. There are two steps to this process:

First, the field space declarations will need to change to accomodate the new partitions. Remember that the nodes have been partitioned three ways: into private, shared, and ghost subregions (example reproduced below). This means that a task which wants to operate for example on the red nodes will need three regions (private, shared, and ghost). This also means that any fspace which points to nodes must be updated to take three arguments as well.

Private (Partition of Nodes) Shared (Partition of Nodes) Ghost (Partition of Nodes)

After this is done, the rest is straightforward: change each task call in the main simulation loop to operate over subregions of the partitions. (Hint: Use a for loop.)

Syntax Guide


In [ ]:
ptr(T, R1, R2, R3) -- Pointer types may point to multiple regions.
P[I] -- Returns the Ith subregion of the partition P.

Exercise


In [ ]:
import "regent"

local c = regentlib.c

struct Currents {
  _0 : float,
  _1 : float,
  _2 : float,
}

struct Voltages {
  _1 : float,
  _2 : float,
}

fspace Node {
  capacitance : float,
  leakage     : float,
  charge      : float,
  voltage     : float,
}

-- TODO: Change Wire to take three arguments: private, shared, and
-- ghost nodes.
fspace Wire(rn : region(Node)) {
  in_node     : ptr(Node, rn),
  out_node    : ptr(Node, rn),
  inductance  : float,
  resistance  : float,
  capacitance : float,
  current     : Currents,
  voltage     : Voltages,
}

local CktConfig = require("session1/circuit_config")
local helper = require("session2/circuit_helper")
local validator = require("session2/circuit_validator")

local WS = 3
local dT = 1e-7

-- TODO: Change this task to take three regions of nodes (private,
-- shared, and ghost).
task calculate_new_currents(steps : uint,
                            rn : region(Node),
                            rw : region(Wire(rn)))
where
  reads(rn.voltage,
        rw.{in_node, out_node, inductance, resistance, capacitance}),
  reads writes(rw.{current, voltage})
do
  var rdT : float = 1.0 / dT
  __demand(__vectorize)
  for w in rw do
    var temp_v : float[WS + 1]
    var temp_i : float[WS]
    var old_i : float[WS]
    var old_v : float[WS - 1]

    temp_i[0] = w.current._0
    temp_i[1] = w.current._1
    temp_i[2] = w.current._2
    for i = 0, WS do old_i[i] = temp_i[i] end

    temp_v[1] = w.voltage._1
    temp_v[2] = w.voltage._2
    for i = 0, WS - 1 do old_v[i] = temp_v[i + 1] end

    -- Pin the outer voltages to the node voltages.
    temp_v[0] = w.in_node.voltage
    temp_v[WS] = w.out_node.voltage

    -- Solve the RLC model iteratively.
    var L : float = w.inductance
    var rR : float = 1.0 / w.resistance
    var rC : float = 1.0 / w.capacitance
    for s = 1, steps + 1 do
      -- First, figure out the new current from the voltage differential
      -- and our inductance:
      -- dV = R*I + L*I' ==> I = (dV - L*I')/R
      for i = 0, WS do
        temp_i[i] = ((temp_v[i + 1] - temp_v[i]) -
                     (L * (temp_i[i] - old_i[i]) * rdT)) * rR
      end
      -- Now update the inter-node voltages.
      for i = 0, WS - 1 do
        temp_v[i + 1] = old_v[i] + dT * (temp_i[i] - temp_i[i + 1]) * rC
      end
    end

    -- Write out the results.
    w.current._0 = temp_i[0]
    w.current._1 = temp_i[1]
    w.current._2 = temp_i[2]

    w.voltage._1 = temp_v[1]
    w.voltage._2 = temp_v[2]
  end
end

-- TODO: Change this task to take three regions of nodes (private,
-- shared, and ghost).
task distribute_charge(rn : region(Node),
                       rw : region(Wire(rn)))
where
  reads(rw.{in_node, out_node, current._0, current._2}),
  reduces +(rn.charge)
do
  for w in rw do
    var in_current = -dT * w.current._0
    var out_current = dT * w.current._2
    w.in_node.charge += in_current
    w.out_node.charge += out_current
  end
end

task update_voltages(rn : region(Node))
where
  reads(rn.{capacitance, leakage}),
  reads writes(rn.{voltage, charge})
do
  for n in rn do
    var voltage = n.voltage + n.charge / n.capacitance
    voltage = voltage * (1.0 - n.leakage)
    n.voltage = voltage
    n.charge = 0.0
  end
end

task toplevel()
  var conf : CktConfig
  conf:initialize_from_command()
  conf:show()

  var num_circuit_nodes = conf.num_pieces * conf.nodes_per_piece
  var num_circuit_wires = conf.num_pieces * conf.wires_per_piece

  var rn = region(ispace(ptr, num_circuit_nodes), Node)
  var rw = region(ispace(ptr, num_circuit_wires), Wire(wild))

  new(ptr(Node, rn), num_circuit_nodes)
  new(ptr(Wire(wild), rw), num_circuit_wires)

  c.printf("Generating a random circuit...\n")
  helper.generate_random_circuit(rn, rw, conf)

  var colors = ispace(int1d, conf.num_pieces)
  var pn_equal = partition(equal, rn, colors)
  var pw = preimage(rw, pn_equal, rw.in_node)
  var pn_extrefs = image(rn, preimage(rw, pn_equal, rw.out_node) - pw, rw.out_node)
  var pn_private = pn_equal - pn_extrefs
  var pn_shared = pn_equal & pn_extrefs
  var pn_ghost = image(rn, pw, rw.out_node) - pn_equal

  helper.dump_graph(conf, rn, rw)
  for i = 0, conf.num_pieces do
    helper.initialize_pointers(pn_private[i], pn_shared[i], pn_ghost[i], pw[i])
  end

  helper.wait_for(helper.block(rn, rw))
  c.printf("Starting main simulation loop\n")
  var ts_start = helper.timestamp()

  for j = 0, conf.num_loops do
    -- TODO: Change each of these calls to work on a piece of the
    -- graph.
    calculate_new_currents(conf.steps, rn, rw)
    distribute_charge(rn, rw)
    update_voltages(rn)
  end

  -- Wait for all previous tasks to complete and measure the elapsed time.
  var _ = 0
  for i = 0, conf.num_pieces do
    _ += helper.block(pn_equal[i], pw[i])
  end
  helper.wait_for(_)
  var ts_end = helper.timestamp()
  c.printf("simulation complete\n")

  var sim_time = 1e-6 * (ts_end - ts_start)
  c.printf("ELAPSED TIME = %7.3f s\n", sim_time)
  var gflops =
    helper.calculate_gflops(sim_time, WS * 6 + (WS - 1) * 4, 4, 4, conf)
  c.printf("GFLOPS = %7.3f GFLOPS\n", gflops)

  c.printf("Validating simulation results...\n")
  validator.validate_solution(rn, rw, conf)
end
regentlib.start(toplevel)

That's it, you've finished the simulation! Hope you've enjoyed the exercise.

Note: There is another session which covers (mapping the computation onto GPUs). However, this machine is not equipped with GPUs, so you won't be able to follow the instructions and see it run. Feel free to take a look at play around with it, if you're still interested.